﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using MessageHandling;
using System.Xml.Schema;

namespace TcpTestServer.DataLayer
{
    class XmlProtocol
    {
        public XmlProtocol( int port_nr, string name)
        {
          _portNr = port_nr;
          _name = name;
          _buf = new MessageBuffer();
          _connected = false;
          _turnedOn = true;
          Thread t = new Thread(new ThreadStart(Service));
          _mainThread = t;
          t.Start();
            

        }
        public bool Connected
        {
            get
            {
                return _connected;
            }
        }
        public bool TurnedOn
        {
            get
            {
                return _connected;
            }
            set
            {
                _turnedOn = value;
            }
        }
        public bool IsReadyToSendMessage
        {
            get
            {
                lock (this)
                {
                    return !_sendingMessage && !_buf.IsFull ;
                }
            }
        }
        public Message MessageBuffer
        {
            set
            {
                lock (this)
                {
                    if (_sendingMessage)
                        throw new InvalidOperationException("Sending previous message");
                    else if(_buf.IsFull)
                        throw new InvalidOperationException("Message buffer is full");
                    else
                        _buf.addMessageToBuffer(value);
                }
            }
        }
        private  MessageStatusArgs sendMessage(Message m)
        {
            byte[] message_bytes = MessageBuilder.ConvertXmlToByteArray(m.Body,System.Text.Encoding.UTF8);
            message_bytes = MessageBuilder.WrapMessage(message_bytes);
            _socket.Send(message_bytes);            
            MessageStatusArgs status = new MessageStatusArgs(MessageStatusEnum.successful,m.Number);
            return status;
        }
        private void sendKeepAliveMessage()
        {
            byte[] message_bytes = MessageBuilder.WrapKeepaliveMessage();
            _socket.Send(message_bytes); 
        }
        private  MessageStatusArgs getReply(Message m)
        {
            ReadMessageAsyncCallback cb = new ReadMessageAsyncCallback(messageCallback);
            ReadMessageAsync async_msg = new ReadMessageAsync(_socket, cb,m.ResponseTimeout);
            
            while (_state == null)
                System.Threading.Thread.Sleep(500);
            if (_state.Error != null)
                throw _state.Error;
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.Schemas.Add(m.Schema);
            settings.ValidationType = ValidationType.Schema;
            XElement input_message = MessageBuilder.ConvertByteArrayToXml(_state.Message,settings);
            ResponseMessageStatusArgs status = new ResponseMessageStatusArgs(MessageStatusEnum.successful, input_message.ToString(),m.Number);
            return status; 

        }
        void messageCallback(ReadMessageEventArgs state)
        {
            _state = state;
        }
        private  void Service()
        {

            while (_turnedOn)
            {
                try
                {
                    IPAddress localAddr = IPAddress.Parse("127.0.0.1");
                    _listener = new TcpListener(localAddr,_portNr);
                    _listener.Start();

                    ConnectionEvent(this, new ConnectionEventArgs(ConnectionStatusEnum.server_set_up, _name, null));
                 
                    _socket = _listener.AcceptSocket();
                    _connected = true;

                    ConnectionEvent(this, new ConnectionEventArgs(ConnectionStatusEnum.connected, _name, _socket.RemoteEndPoint.ToString()));


                    while (true)
                    {
                        if (_buf.HasMessage == false)
                        {
                            Thread.Sleep(_sleepingTime);
                            sendKeepAliveMessage();
                        }
                        else
                        {
                            _sendingMessage = true;
                            Message current_message = _buf.getMessage();
                            MessageSent(this, sendMessage(current_message));
                            try
                            {
                                ResponseReceived(this, getReply(current_message));
                            }
                            catch (TimeoutException ex)
                            {
                                ResponseReceived(this, new ResponseMessageStatusArgs(MessageStatusEnum.failure, ex.Message, current_message.Number));
                                continue;
                            }
                            catch (XmlSchemaValidationException ex)
                            {
                                ResponseReceived(this, new ResponseMessageStatusArgs(MessageStatusEnum.failure, ex.Message, current_message.Number));
                                continue;
                            }
                            finally
                            {
                                _state = null;
                            }
                            _buf.disposeMessage();
                            _sendingMessage = false;
                        }

                    }

                }
                catch (Exception e)
                {
                    _connected = false;
                    _sendingMessage = false;
                    ConnectionEvent(this, new ConnectionEventArgs(ConnectionStatusEnum.connection_error, _name, null, e.Message));
                    Thread.Sleep(1500);
                }
                finally
                {
                    _socket.Close();
                    _listener.Stop();
                }
            }

            //Console.WriteLine("Disconnected: {0}", _socket.RemoteEndPoint);

               // soc.Close();
            
        }   

        #region events
        public event MessageCallbackDel MessageSent;
        public event MessageCallbackDel ResponseReceived;
        public event ConnectionDel ConnectionEvent;
        #endregion

        #region private_variables
        ReadMessageEventArgs _state;
        private TcpListener _listener;
        private Thread _mainThread;
        private Socket _socket;
        private MessageBuffer _buf;
        private int _portNr;
        private string _name;
        private bool _connected;
        private bool _turnedOn;
        private bool _sendingMessage;
        private const int _sleepingTime = 1000;
        #endregion
       
    }
}
